/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.beans.beancontext;

import java.awt.Component;
import java.awt.Container;

import java.beans.Beans;
import java.beans.AppletInitializer;

import java.beans.DesignMode;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.beans.PropertyVetoException;

import java.beans.Visibility;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import java.net.URL;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;


/**
 * This helper class provides a utility implementation of the
 * java.beans.beancontext.BeanContext interface.
 * <p>
 * Since this class directly implements the BeanContext interface, the class
 * can, and is intended to be used either by subclassing this implementation,
 * or via ad-hoc delegation of an instance of this class from another.
 * </p>
 *
 * @author Laurence P. G. Cable
 * @since 1.2
 */
public class BeanContextSupport extends BeanContextChildSupport
    implements BeanContext,
    Serializable,
    PropertyChangeListener,
    VetoableChangeListener {

  // Fix for bug 4282900 to pass JCK regression test
  static final long serialVersionUID = -4879613978649577204L;

  /**
   * Construct a BeanContextSupport instance
   *
   * @param peer The peer <tt>BeanContext</tt> we are supplying an implementation for, or
   * <tt>null</tt> if this object is its own peer
   * @param lcle The current Locale for this BeanContext. If <tt>lcle</tt> is <tt>null</tt>, the
   * default locale is assigned to the <tt>BeanContext</tt> instance.
   * @param dTime The initial state, <tt>true</tt> if in design mode, <tt>false</tt> if runtime.
   * @param visible The initial visibility.
   * @see java.util.Locale#getDefault()
   * @see java.util.Locale#setDefault(java.util.Locale)
   */
  public BeanContextSupport(BeanContext peer, Locale lcle, boolean dTime, boolean visible) {
    super(peer);

    locale = lcle != null ? lcle : Locale.getDefault();
    designTime = dTime;
    okToUseGui = visible;

    initialize();
  }

  /**
   * Create an instance using the specified Locale and design mode.
   *
   * @param peer The peer <tt>BeanContext</tt> we are supplying an implementation for, or
   * <tt>null</tt> if this object is its own peer
   * @param lcle The current Locale for this <tt>BeanContext</tt>. If <tt>lcle</tt> is
   * <tt>null</tt>, the default locale is assigned to the <tt>BeanContext</tt> instance.
   * @param dtime The initial state, <tt>true</tt> if in design mode, <tt>false</tt> if runtime.
   * @see java.util.Locale#getDefault()
   * @see java.util.Locale#setDefault(java.util.Locale)
   */
  public BeanContextSupport(BeanContext peer, Locale lcle, boolean dtime) {
    this(peer, lcle, dtime, true);
  }

  /**
   * Create an instance using the specified locale
   *
   * @param peer The peer BeanContext we are supplying an implementation for, or <tt>null</tt> if
   * this object is its own peer
   * @param lcle The current Locale for this <tt>BeanContext</tt>. If <tt>lcle</tt> is
   * <tt>null</tt>, the default locale is assigned to the <tt>BeanContext</tt> instance.
   * @see java.util.Locale#getDefault()
   * @see java.util.Locale#setDefault(java.util.Locale)
   */
  public BeanContextSupport(BeanContext peer, Locale lcle) {
    this(peer, lcle, false, true);
  }

  /**
   * Create an instance using with a default locale
   *
   * @param peer The peer <tt>BeanContext</tt> we are supplying an implementation for, or
   * <tt>null</tt> if this object is its own peer
   */
  public BeanContextSupport(BeanContext peer) {
    this(peer, null, false, true);
  }

  /**
   * Create an instance that is not a delegate of another object
   */

  public BeanContextSupport() {
    this(null, null, false, true);
  }

  /**
   * Gets the instance of <tt>BeanContext</tt> that
   * this object is providing the implementation for.
   *
   * @return the BeanContext instance
   */
  public BeanContext getBeanContextPeer() {
    return (BeanContext) getBeanContextChildPeer();
  }

  /**
   * <p>
   * The instantiateChild method is a convenience hook
   * in BeanContext to simplify
   * the task of instantiating a Bean, nested,
   * into a <tt>BeanContext</tt>.
   * </p>
   * <p>
   * The semantics of the beanName parameter are defined by java.beans.Beans.instantiate.
   * </p>
   *
   * @param beanName the name of the Bean to instantiate within this BeanContext
   * @return the new object
   * @throws IOException if there is an I/O error when the bean is being deserialized
   * @throws ClassNotFoundException if the class identified by the beanName parameter is not found
   */
  public Object instantiateChild(String beanName)
      throws IOException, ClassNotFoundException {
    BeanContext bc = getBeanContextPeer();

    return Beans.instantiate(bc.getClass().getClassLoader(), beanName, bc);
  }

  /**
   * Gets the number of children currently nested in
   * this BeanContext.
   *
   * @return number of children
   */
  public int size() {
    synchronized (children) {
      return children.size();
    }
  }

  /**
   * Reports whether or not this
   * <tt>BeanContext</tt> is empty.
   * A <tt>BeanContext</tt> is considered
   * empty when it contains zero
   * nested children.
   *
   * @return if there are not children
   */
  public boolean isEmpty() {
    synchronized (children) {
      return children.isEmpty();
    }
  }

  /**
   * Determines whether or not the specified object
   * is currently a child of this <tt>BeanContext</tt>.
   *
   * @param o the Object in question
   * @return if this object is a child
   */
  public boolean contains(Object o) {
    synchronized (children) {
      return children.containsKey(o);
    }
  }

  /**
   * Determines whether or not the specified object
   * is currently a child of this <tt>BeanContext</tt>.
   *
   * @param o the Object in question
   * @return if this object is a child
   */
  public boolean containsKey(Object o) {
    synchronized (children) {
      return children.containsKey(o);
    }
  }

  /**
   * Gets all JavaBean or <tt>BeanContext</tt> instances
   * currently nested in this <tt>BeanContext</tt>.
   *
   * @return an <tt>Iterator</tt> of the nested children
   */
  public Iterator iterator() {
    synchronized (children) {
      return new BCSIterator(children.keySet().iterator());
    }
  }

  /**
   * Gets all JavaBean or <tt>BeanContext</tt>
   * instances currently nested in this BeanContext.
   */
  public Object[] toArray() {
    synchronized (children) {
      return children.keySet().toArray();
    }
  }

  /**
   * Gets an array containing all children of
   * this <tt>BeanContext</tt> that match
   * the types contained in arry.
   *
   * @param arry The array of object types that are of interest.
   * @return an array of children
   */
  public Object[] toArray(Object[] arry) {
    synchronized (children) {
      return children.keySet().toArray(arry);
    }
  }

  /************************************************************************/

  /**
   * protected final subclass that encapsulates an iterator but implements
   * a noop remove() method.
   */

  protected static final class BCSIterator implements Iterator {

    BCSIterator(Iterator i) {
      super();
      src = i;
    }

    public boolean hasNext() {
      return src.hasNext();
    }

    public Object next() {
      return src.next();
    }

    public void remove() { /* do nothing */ }

    private Iterator src;
  }

  /************************************************************************/

    /*
     * protected nested class containing per child information, an instance
     * of which is associated with each child in the "children" hashtable.
     * subclasses can extend this class to include their own per-child state.
     *
     * Note that this 'value' is serialized with the corresponding child 'key'
     * when the BeanContextSupport is serialized.
     */

  protected class BCSChild implements Serializable {

    private static final long serialVersionUID = -5815286101609939109L;

    BCSChild(Object bcc, Object peer) {
      super();

      child = bcc;
      proxyPeer = peer;
    }

    Object getChild() {
      return child;
    }

    void setRemovePending(boolean v) {
      removePending = v;
    }

    boolean isRemovePending() {
      return removePending;
    }

    boolean isProxyPeer() {
      return proxyPeer != null;
    }

    Object getProxyPeer() {
      return proxyPeer;
    }
        /*
         * fields
         */


    private Object child;
    private Object proxyPeer;

    private transient boolean removePending;
  }

  /**
   * <p>
   * Subclasses can override this method to insert their own subclass
   * of Child without having to override add() or the other Collection
   * methods that add children to the set.
   * </p>
   *
   * @param targetChild the child to create the Child on behalf of
   * @param peer the peer if the tragetChild and the peer are related by an implementation of
   * BeanContextProxy     * @return Subtype-specific subclass of Child without overriding collection
   * methods
   */

  protected BCSChild createBCSChild(Object targetChild, Object peer) {
    return new BCSChild(targetChild, peer);
  }

  /************************************************************************/

  /**
   * Adds/nests a child within this <tt>BeanContext</tt>.
   * <p>
   * Invoked as a side effect of java.beans.Beans.instantiate().
   * If the child object is not valid for adding then this method
   * throws an IllegalStateException.
   * </p>
   *
   * @param targetChild The child objects to nest within this <tt>BeanContext</tt>
   * @return true if the child was added successfully.
   * @see #validatePendingAdd
   */
  public boolean add(Object targetChild) {

    if (targetChild == null) {
      throw new IllegalArgumentException();
    }

    // The specification requires that we do nothing if the child
    // is already nested herein.

    if (children.containsKey(targetChild)) {
      return false; // test before locking
    }

    synchronized (BeanContext.globalHierarchyLock) {
      if (children.containsKey(targetChild)) {
        return false; // check again
      }

      if (!validatePendingAdd(targetChild)) {
        throw new IllegalStateException();
      }

      // The specification requires that we invoke setBeanContext() on the
      // newly added child if it implements the java.beans.beancontext.BeanContextChild interface

      BeanContextChild cbcc = getChildBeanContextChild(targetChild);
      BeanContextChild bccp = null;

      synchronized (targetChild) {

        if (targetChild instanceof BeanContextProxy) {
          bccp = ((BeanContextProxy) targetChild).getBeanContextProxy();

          if (bccp == null) {
            throw new NullPointerException("BeanContextPeer.getBeanContextProxy()");
          }
        }

        BCSChild bcsc = createBCSChild(targetChild, bccp);
        BCSChild pbcsc = null;

        synchronized (children) {
          children.put(targetChild, bcsc);

          if (bccp != null) {
            children.put(bccp, pbcsc = createBCSChild(bccp, targetChild));
          }
        }

        if (cbcc != null) {
          synchronized (cbcc) {
            try {
              cbcc.setBeanContext(getBeanContextPeer());
            } catch (PropertyVetoException pve) {

              synchronized (children) {
                children.remove(targetChild);

                if (bccp != null) {
                  children.remove(bccp);
                }
              }

              throw new IllegalStateException();
            }

            cbcc.addPropertyChangeListener("beanContext", childPCL);
            cbcc.addVetoableChangeListener("beanContext", childVCL);
          }
        }

        Visibility v = getChildVisibility(targetChild);

        if (v != null) {
          if (okToUseGui) {
            v.okToUseGui();
          } else {
            v.dontUseGui();
          }
        }

        if (getChildSerializable(targetChild) != null) {
          serializable++;
        }

        childJustAddedHook(targetChild, bcsc);

        if (bccp != null) {
          v = getChildVisibility(bccp);

          if (v != null) {
            if (okToUseGui) {
              v.okToUseGui();
            } else {
              v.dontUseGui();
            }
          }

          if (getChildSerializable(bccp) != null) {
            serializable++;
          }

          childJustAddedHook(bccp, pbcsc);
        }


      }

      // The specification requires that we fire a notification of the change

      fireChildrenAdded(new BeanContextMembershipEvent(getBeanContextPeer(),
          bccp == null ? new Object[]{targetChild} : new Object[]{targetChild, bccp}));

    }

    return true;
  }

  /**
   * Removes a child from this BeanContext.  If the child object is not
   * for adding then this method throws an IllegalStateException.
   *
   * @param targetChild The child objects to remove
   * @see #validatePendingRemove
   */
  public boolean remove(Object targetChild) {
    return remove(targetChild, true);
  }

  /**
   * internal remove used when removal caused by
   * unexpected <tt>setBeanContext</tt> or
   * by <tt>remove()</tt> invocation.
   *
   * @param targetChild the JavaBean, BeanContext, or Object to be removed
   * @param callChildSetBC used to indicate that the child should be notified that it is no longer
   * nested in this <tt>BeanContext</tt>.
   * @return whether or not was present before being removed
   */
  protected boolean remove(Object targetChild, boolean callChildSetBC) {

    if (targetChild == null) {
      throw new IllegalArgumentException();
    }

    synchronized (BeanContext.globalHierarchyLock) {
      if (!containsKey(targetChild)) {
        return false;
      }

      if (!validatePendingRemove(targetChild)) {
        throw new IllegalStateException();
      }

      BCSChild bcsc = (BCSChild) children.get(targetChild);
      BCSChild pbcsc = null;
      Object peer = null;

      // we are required to notify the child that it is no longer nested here if
      // it implements java.beans.beancontext.BeanContextChild

      synchronized (targetChild) {
        if (callChildSetBC) {
          BeanContextChild cbcc = getChildBeanContextChild(targetChild);
          if (cbcc != null) {
            synchronized (cbcc) {
              cbcc.removePropertyChangeListener("beanContext", childPCL);
              cbcc.removeVetoableChangeListener("beanContext", childVCL);

              try {
                cbcc.setBeanContext(null);
              } catch (PropertyVetoException pve1) {
                cbcc.addPropertyChangeListener("beanContext", childPCL);
                cbcc.addVetoableChangeListener("beanContext", childVCL);
                throw new IllegalStateException();
              }

            }
          }
        }

        synchronized (children) {
          children.remove(targetChild);

          if (bcsc.isProxyPeer()) {
            pbcsc = (BCSChild) children.get(peer = bcsc.getProxyPeer());
            children.remove(peer);
          }
        }

        if (getChildSerializable(targetChild) != null) {
          serializable--;
        }

        childJustRemovedHook(targetChild, bcsc);

        if (peer != null) {
          if (getChildSerializable(peer) != null) {
            serializable--;
          }

          childJustRemovedHook(peer, pbcsc);
        }
      }

      fireChildrenRemoved(new BeanContextMembershipEvent(getBeanContextPeer(),
          peer == null ? new Object[]{targetChild} : new Object[]{targetChild, peer}));

    }

    return true;
  }

  /**
   * Tests to see if all objects in the
   * specified <tt>Collection</tt> are children of
   * this <tt>BeanContext</tt>.
   *
   * @param c the specified <tt>Collection</tt>
   * @return <tt>true</tt> if all objects in the collection are children of this
   * <tt>BeanContext</tt>, false if not.
   */
  public boolean containsAll(Collection c) {
    synchronized (children) {
      Iterator i = c.iterator();
      while (i.hasNext()) {
        if (!contains(i.next())) {
          return false;
        }
      }

      return true;
    }
  }

  /**
   * add Collection to set of Children (Unsupported)
   * implementations must synchronized on the hierarchy lock and "children" protected field
   *
   * @return this implementation unconditionally throws {@code UnsupportedOperationException}
   * @throws UnsupportedOperationException thrown unconditionally by this implementation
   */
  public boolean addAll(Collection c) {
    throw new UnsupportedOperationException();
  }

  /**
   * remove all specified children (Unsupported)
   * implementations must synchronized on the hierarchy lock and "children" protected field
   *
   * @return this implementation unconditionally throws {@code UnsupportedOperationException}
   * @throws UnsupportedOperationException thrown unconditionally by this implementation
   */
  public boolean removeAll(Collection c) {
    throw new UnsupportedOperationException();
  }


  /**
   * retain only specified children (Unsupported)
   * implementations must synchronized on the hierarchy lock and "children" protected field
   *
   * @return this implementation unconditionally throws {@code UnsupportedOperationException}
   * @throws UnsupportedOperationException thrown unconditionally by this implementation
   */
  public boolean retainAll(Collection c) {
    throw new UnsupportedOperationException();
  }

  /**
   * clear the children (Unsupported)
   * implementations must synchronized on the hierarchy lock and "children" protected field
   *
   * @throws UnsupportedOperationException thrown unconditionally by this implementation
   */
  public void clear() {
    throw new UnsupportedOperationException();
  }

  /**
   * Adds a BeanContextMembershipListener
   *
   * @param bcml the BeanContextMembershipListener to add
   * @throws NullPointerException if the argument is null
   */

  public void addBeanContextMembershipListener(BeanContextMembershipListener bcml) {
    if (bcml == null) {
      throw new NullPointerException("listener");
    }

    synchronized (bcmListeners) {
      if (bcmListeners.contains(bcml)) {
        return;
      } else {
        bcmListeners.add(bcml);
      }
    }
  }

  /**
   * Removes a BeanContextMembershipListener
   *
   * @param bcml the BeanContextMembershipListener to remove
   * @throws NullPointerException if the argument is null
   */

  public void removeBeanContextMembershipListener(BeanContextMembershipListener bcml) {
    if (bcml == null) {
      throw new NullPointerException("listener");
    }

    synchronized (bcmListeners) {
      if (!bcmListeners.contains(bcml)) {
        return;
      } else {
        bcmListeners.remove(bcml);
      }
    }
  }

  /**
   * @param name the name of the resource requested.
   * @param bcc the child object making the request.
   * @return the requested resource as an InputStream
   * @throws NullPointerException if the argument is null
   */

  public InputStream getResourceAsStream(String name, BeanContextChild bcc) {
    if (name == null) {
      throw new NullPointerException("name");
    }
    if (bcc == null) {
      throw new NullPointerException("bcc");
    }

    if (containsKey(bcc)) {
      ClassLoader cl = bcc.getClass().getClassLoader();

      return cl != null ? cl.getResourceAsStream(name)
          : ClassLoader.getSystemResourceAsStream(name);
    } else {
      throw new IllegalArgumentException("Not a valid child");
    }
  }

  /**
   * @param name the name of the resource requested.
   * @param bcc the child object making the request.
   * @return the requested resource as an InputStream
   */

  public URL getResource(String name, BeanContextChild bcc) {
    if (name == null) {
      throw new NullPointerException("name");
    }
    if (bcc == null) {
      throw new NullPointerException("bcc");
    }

    if (containsKey(bcc)) {
      ClassLoader cl = bcc.getClass().getClassLoader();

      return cl != null ? cl.getResource(name)
          : ClassLoader.getSystemResource(name);
    } else {
      throw new IllegalArgumentException("Not a valid child");
    }
  }

  /**
   * Sets the new design time value for this <tt>BeanContext</tt>.
   *
   * @param dTime the new designTime value
   */
  public synchronized void setDesignTime(boolean dTime) {
    if (designTime != dTime) {
      designTime = dTime;

      firePropertyChange("designMode", Boolean.valueOf(!dTime), Boolean.valueOf(dTime));
    }
  }


  /**
   * Reports whether or not this object is in
   * currently in design time mode.
   *
   * @return <tt>true</tt> if in design time mode, <tt>false</tt> if not
   */
  public synchronized boolean isDesignTime() {
    return designTime;
  }

  /**
   * Sets the locale of this BeanContext.
   *
   * @param newLocale the new locale. This method call will have no effect if newLocale is
   * <CODE>null</CODE>.
   * @throws PropertyVetoException if the new value is rejected
   */
  public synchronized void setLocale(Locale newLocale) throws PropertyVetoException {

    if ((locale != null && !locale.equals(newLocale)) && newLocale != null) {
      Locale old = locale;

      fireVetoableChange("locale", old, newLocale); // throws

      locale = newLocale;

      firePropertyChange("locale", old, newLocale);
    }
  }

  /**
   * Gets the locale for this <tt>BeanContext</tt>.
   *
   * @return the current Locale of the <tt>BeanContext</tt>
   */
  public synchronized Locale getLocale() {
    return locale;
  }

  /**
   * <p>
   * This method is typically called from the environment in order to determine
   * if the implementor "needs" a GUI.
   * </p>
   * <p>
   * The algorithm used herein tests the BeanContextPeer, and its current children
   * to determine if they are either Containers, Components, or if they implement
   * Visibility and return needsGui() == true.
   * </p>
   *
   * @return <tt>true</tt> if the implementor needs a GUI
   */
  public synchronized boolean needsGui() {
    BeanContext bc = getBeanContextPeer();

    if (bc != this) {
      if (bc instanceof Visibility) {
        return ((Visibility) bc).needsGui();
      }

      if (bc instanceof Container || bc instanceof Component) {
        return true;
      }
    }

    synchronized (children) {
      for (Iterator i = children.keySet().iterator(); i.hasNext(); ) {
        Object c = i.next();

        try {
          return ((Visibility) c).needsGui();
        } catch (ClassCastException cce) {
          // do nothing ...
        }

        if (c instanceof Container || c instanceof Component) {
          return true;
        }
      }
    }

    return false;
  }

  /**
   * notify this instance that it may no longer render a GUI.
   */

  public synchronized void dontUseGui() {
    if (okToUseGui) {
      okToUseGui = false;

      // lets also tell the Children that can that they may not use their GUI's
      synchronized (children) {
        for (Iterator i = children.keySet().iterator(); i.hasNext(); ) {
          Visibility v = getChildVisibility(i.next());

          if (v != null) {
            v.dontUseGui();
          }
        }
      }
    }
  }

  /**
   * Notify this instance that it may now render a GUI
   */

  public synchronized void okToUseGui() {
    if (!okToUseGui) {
      okToUseGui = true;

      // lets also tell the Children that can that they may use their GUI's
      synchronized (children) {
        for (Iterator i = children.keySet().iterator(); i.hasNext(); ) {
          Visibility v = getChildVisibility(i.next());

          if (v != null) {
            v.okToUseGui();
          }
        }
      }
    }
  }

  /**
   * Used to determine if the <tt>BeanContext</tt>
   * child is avoiding using its GUI.
   *
   * @return is this instance avoiding using its GUI?
   * @see Visibility
   */
  public boolean avoidingGui() {
    return !okToUseGui && needsGui();
  }

  /**
   * Is this <tt>BeanContext</tt> in the
   * process of being serialized?
   *
   * @return if this <tt>BeanContext</tt> is currently being serialized
   */
  public boolean isSerializing() {
    return serializing;
  }

  /**
   * Returns an iterator of all children
   * of this <tt>BeanContext</tt>.
   *
   * @return an iterator for all the current BCSChild values
   */
  protected Iterator bcsChildren() {
    synchronized (children) {
      return children.values().iterator();
    }
  }

  /**
   * called by writeObject after defaultWriteObject() but prior to
   * serialization of currently serializable children.
   *
   * This method may be overridden by subclasses to perform custom
   * serialization of their state prior to this superclass serializing
   * the children.
   *
   * This method should not however be used by subclasses to replace their
   * own implementation (if any) of writeObject().
   *
   * @param oos the {@code ObjectOutputStream} to use during serialization
   * @throws IOException if serialization failed
   */

  protected void bcsPreSerializationHook(ObjectOutputStream oos) throws IOException {
  }

  /**
   * called by readObject after defaultReadObject() but prior to
   * deserialization of any children.
   *
   * This method may be overridden by subclasses to perform custom
   * deserialization of their state prior to this superclass deserializing
   * the children.
   *
   * This method should not however be used by subclasses to replace their
   * own implementation (if any) of readObject().
   *
   * @param ois the {@code ObjectInputStream} to use during deserialization
   * @throws IOException if deserialization failed
   * @throws ClassNotFoundException if needed classes are not found
   */

  protected void bcsPreDeserializationHook(ObjectInputStream ois)
      throws IOException, ClassNotFoundException {
  }

  /**
   * Called by readObject with the newly deserialized child and BCSChild.
   *
   * @param child the newly deserialized child
   * @param bcsc the newly deserialized BCSChild
   */
  protected void childDeserializedHook(Object child, BCSChild bcsc) {
    synchronized (children) {
      children.put(child, bcsc);
    }
  }

  /**
   * Used by writeObject to serialize a Collection.
   *
   * @param oos the <tt>ObjectOutputStream</tt> to use during serialization
   * @param coll the <tt>Collection</tt> to serialize
   * @throws IOException if serialization failed
   */
  protected final void serialize(ObjectOutputStream oos, Collection coll) throws IOException {
    int count = 0;
    Object[] objects = coll.toArray();

    for (int i = 0; i < objects.length; i++) {
      if (objects[i] instanceof Serializable) {
        count++;
      } else {
        objects[i] = null;
      }
    }

    oos.writeInt(count); // number of subsequent objects

    for (int i = 0; count > 0; i++) {
      Object o = objects[i];

      if (o != null) {
        oos.writeObject(o);
        count--;
      }
    }
  }

  /**
   * used by readObject to deserialize a collection.
   *
   * @param ois the ObjectInputStream to use
   * @param coll the Collection
   * @throws IOException if deserialization failed
   * @throws ClassNotFoundException if needed classes are not found
   */
  protected final void deserialize(ObjectInputStream ois, Collection coll)
      throws IOException, ClassNotFoundException {
    int count = 0;

    count = ois.readInt();

    while (count-- > 0) {
      coll.add(ois.readObject());
    }
  }

  /**
   * Used to serialize all children of
   * this <tt>BeanContext</tt>.
   *
   * @param oos the <tt>ObjectOutputStream</tt> to use during serialization
   * @throws IOException if serialization failed
   */
  public final void writeChildren(ObjectOutputStream oos) throws IOException {
    if (serializable <= 0) {
      return;
    }

    boolean prev = serializing;

    serializing = true;

    int count = 0;

    synchronized (children) {
      Iterator i = children.entrySet().iterator();

      while (i.hasNext() && count < serializable) {
        Map.Entry entry = (Map.Entry) i.next();

        if (entry.getKey() instanceof Serializable) {
          try {
            oos.writeObject(entry.getKey());   // child
            oos.writeObject(entry.getValue()); // BCSChild
          } catch (IOException ioe) {
            serializing = prev;
            throw ioe;
          }
          count++;
        }
      }
    }

    serializing = prev;

    if (count != serializable) {
      throw new IOException("wrote different number of children than expected");
    }

  }

  /**
   * Serialize the BeanContextSupport, if this instance has a distinct
   * peer (that is this object is acting as a delegate for another) then
   * the children of this instance are not serialized here due to a
   * 'chicken and egg' problem that occurs on deserialization of the
   * children at the same time as this instance.
   *
   * Therefore in situations where there is a distinct peer to this instance
   * it should always call writeObject() followed by writeChildren() and
   * readObject() followed by readChildren().
   *
   * @param oos the ObjectOutputStream
   */

  private synchronized void writeObject(ObjectOutputStream oos)
      throws IOException, ClassNotFoundException {
    serializing = true;

    synchronized (BeanContext.globalHierarchyLock) {
      try {
        oos.defaultWriteObject(); // serialize the BeanContextSupport object

        bcsPreSerializationHook(oos);

        if (serializable > 0 && this.equals(getBeanContextPeer())) {
          writeChildren(oos);
        }

        serialize(oos, (Collection) bcmListeners);
      } finally {
        serializing = false;
      }
    }
  }

  /**
   * When an instance of this class is used as a delegate for the
   * implementation of the BeanContext protocols (and its subprotocols)
   * there exists a 'chicken and egg' problem during deserialization
   *
   * @param ois the ObjectInputStream to use
   * @throws IOException if deserialization failed
   * @throws ClassNotFoundException if needed classes are not found
   */

  public final void readChildren(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    int count = serializable;

    while (count-- > 0) {
      Object child = null;
      BeanContextSupport.BCSChild bscc = null;

      try {
        child = ois.readObject();
        bscc = (BeanContextSupport.BCSChild) ois.readObject();
      } catch (IOException ioe) {
        continue;
      } catch (ClassNotFoundException cnfe) {
        continue;
      }

      synchronized (child) {
        BeanContextChild bcc = null;

        try {
          bcc = (BeanContextChild) child;
        } catch (ClassCastException cce) {
          // do nothing;
        }

        if (bcc != null) {
          try {
            bcc.setBeanContext(getBeanContextPeer());

            bcc.addPropertyChangeListener("beanContext", childPCL);
            bcc.addVetoableChangeListener("beanContext", childVCL);

          } catch (PropertyVetoException pve) {
            continue;
          }
        }

        childDeserializedHook(child, bscc);
      }
    }
  }

  /**
   * deserialize contents ... if this instance has a distinct peer the
   * children are *not* serialized here, the peer's readObject() must call
   * readChildren() after deserializing this instance.
   */

  private synchronized void readObject(ObjectInputStream ois)
      throws IOException, ClassNotFoundException {

    synchronized (BeanContext.globalHierarchyLock) {
      ois.defaultReadObject();

      initialize();

      bcsPreDeserializationHook(ois);

      if (serializable > 0 && this.equals(getBeanContextPeer())) {
        readChildren(ois);
      }

      deserialize(ois, bcmListeners = new ArrayList(1));
    }
  }

  /**
   * subclasses may envelope to monitor veto child property changes.
   */

  public void vetoableChange(PropertyChangeEvent pce) throws PropertyVetoException {
    String propertyName = pce.getPropertyName();
    Object source = pce.getSource();

    synchronized (children) {
      if ("beanContext".equals(propertyName) &&
          containsKey(source) &&
          !getBeanContextPeer().equals(pce.getNewValue())
          ) {
        if (!validatePendingRemove(source)) {
          throw new PropertyVetoException("current BeanContext vetoes setBeanContext()", pce);
        } else {
          ((BCSChild) children.get(source)).setRemovePending(true);
        }
      }
    }
  }

  /**
   * subclasses may envelope to monitor child property changes.
   */

  public void propertyChange(PropertyChangeEvent pce) {
    String propertyName = pce.getPropertyName();
    Object source = pce.getSource();

    synchronized (children) {
      if ("beanContext".equals(propertyName) &&
          containsKey(source) &&
          ((BCSChild) children.get(source)).isRemovePending()) {
        BeanContext bc = getBeanContextPeer();

        if (bc.equals(pce.getOldValue()) && !bc.equals(pce.getNewValue())) {
          remove(source, false);
        } else {
          ((BCSChild) children.get(source)).setRemovePending(false);
        }
      }
    }
  }

  /**
   * <p>
   * Subclasses of this class may override, or envelope, this method to
   * add validation behavior for the BeanContext to examine child objects
   * immediately prior to their being added to the BeanContext.
   * </p>
   *
   * @param targetChild the child to create the Child on behalf of
   * @return true iff the child may be added to this BeanContext, otherwise false.
   */

  protected boolean validatePendingAdd(Object targetChild) {
    return true;
  }

  /**
   * <p>
   * Subclasses of this class may override, or envelope, this method to
   * add validation behavior for the BeanContext to examine child objects
   * immediately prior to their being removed from the BeanContext.
   * </p>
   *
   * @param targetChild the child to create the Child on behalf of
   * @return true iff the child may be removed from this BeanContext, otherwise false.
   */

  protected boolean validatePendingRemove(Object targetChild) {
    return true;
  }

  /**
   * subclasses may override this method to simply extend add() semantics
   * after the child has been added and before the event notification has
   * occurred. The method is called with the child synchronized.
   *
   * @param child the child
   * @param bcsc the BCSChild
   */

  protected void childJustAddedHook(Object child, BCSChild bcsc) {
  }

  /**
   * subclasses may override this method to simply extend remove() semantics
   * after the child has been removed and before the event notification has
   * occurred. The method is called with the child synchronized.
   *
   * @param child the child
   * @param bcsc the BCSChild
   */

  protected void childJustRemovedHook(Object child, BCSChild bcsc) {
  }

  /**
   * Gets the Component (if any) associated with the specified child.
   *
   * @param child the specified child
   * @return the Component (if any) associated with the specified child.
   */
  protected static final Visibility getChildVisibility(Object child) {
    try {
      return (Visibility) child;
    } catch (ClassCastException cce) {
      return null;
    }
  }

  /**
   * Gets the Serializable (if any) associated with the specified Child
   *
   * @param child the specified child
   * @return the Serializable (if any) associated with the specified Child
   */
  protected static final Serializable getChildSerializable(Object child) {
    try {
      return (Serializable) child;
    } catch (ClassCastException cce) {
      return null;
    }
  }

  /**
   * Gets the PropertyChangeListener
   * (if any) of the specified child
   *
   * @param child the specified child
   * @return the PropertyChangeListener (if any) of the specified child
   */
  protected static final PropertyChangeListener getChildPropertyChangeListener(Object child) {
    try {
      return (PropertyChangeListener) child;
    } catch (ClassCastException cce) {
      return null;
    }
  }

  /**
   * Gets the VetoableChangeListener
   * (if any) of the specified child
   *
   * @param child the specified child
   * @return the VetoableChangeListener (if any) of the specified child
   */
  protected static final VetoableChangeListener getChildVetoableChangeListener(Object child) {
    try {
      return (VetoableChangeListener) child;
    } catch (ClassCastException cce) {
      return null;
    }
  }

  /**
   * Gets the BeanContextMembershipListener
   * (if any) of the specified child
   *
   * @param child the specified child
   * @return the BeanContextMembershipListener (if any) of the specified child
   */
  protected static final BeanContextMembershipListener getChildBeanContextMembershipListener(
      Object child) {
    try {
      return (BeanContextMembershipListener) child;
    } catch (ClassCastException cce) {
      return null;
    }
  }

  /**
   * Gets the BeanContextChild (if any) of the specified child
   *
   * @param child the specified child
   * @return the BeanContextChild (if any) of the specified child
   * @throws IllegalArgumentException if child implements both BeanContextChild and
   * BeanContextProxy
   */
  protected static final BeanContextChild getChildBeanContextChild(Object child) {
    try {
      BeanContextChild bcc = (BeanContextChild) child;

      if (child instanceof BeanContextChild && child instanceof BeanContextProxy) {
        throw new IllegalArgumentException(
            "child cannot implement both BeanContextChild and BeanContextProxy");
      } else {
        return bcc;
      }
    } catch (ClassCastException cce) {
      try {
        return ((BeanContextProxy) child).getBeanContextProxy();
      } catch (ClassCastException cce1) {
        return null;
      }
    }
  }

  /**
   * Fire a BeanContextshipEvent on the BeanContextMembershipListener interface
   *
   * @param bcme the event to fire
   */

  protected final void fireChildrenAdded(BeanContextMembershipEvent bcme) {
    Object[] copy;

    synchronized (bcmListeners) {
      copy = bcmListeners.toArray();
    }

    for (int i = 0; i < copy.length; i++) {
      ((BeanContextMembershipListener) copy[i]).childrenAdded(bcme);
    }
  }

  /**
   * Fire a BeanContextshipEvent on the BeanContextMembershipListener interface
   *
   * @param bcme the event to fire
   */

  protected final void fireChildrenRemoved(BeanContextMembershipEvent bcme) {
    Object[] copy;

    synchronized (bcmListeners) {
      copy = bcmListeners.toArray();
    }

    for (int i = 0; i < copy.length; i++) {
      ((BeanContextMembershipListener) copy[i]).childrenRemoved(bcme);
    }
  }

  /**
   * protected method called from constructor and readObject to initialize
   * transient state of BeanContextSupport instance.
   *
   * This class uses this method to instantiate inner class listeners used
   * to monitor PropertyChange and VetoableChange events on children.
   *
   * subclasses may envelope this method to add their own initialization
   * behavior
   */

  protected synchronized void initialize() {
    children = new HashMap(serializable + 1);
    bcmListeners = new ArrayList(1);

    childPCL = new PropertyChangeListener() {

            /*
             * this adaptor is used by the BeanContextSupport class to forward
             * property changes from a child to the BeanContext, avoiding
             * accidential serialization of the BeanContext by a badly
             * behaved Serializable child.
             */

      public void propertyChange(PropertyChangeEvent pce) {
        BeanContextSupport.this.propertyChange(pce);
      }
    };

    childVCL = new VetoableChangeListener() {

            /*
             * this adaptor is used by the BeanContextSupport class to forward
             * vetoable changes from a child to the BeanContext, avoiding
             * accidential serialization of the BeanContext by a badly
             * behaved Serializable child.
             */

      public void vetoableChange(PropertyChangeEvent pce) throws PropertyVetoException {
        BeanContextSupport.this.vetoableChange(pce);
      }
    };
  }

  /**
   * Gets a copy of the this BeanContext's children.
   *
   * @return a copy of the current nested children
   */
  protected final Object[] copyChildren() {
    synchronized (children) {
      return children.keySet().toArray();
    }
  }

  /**
   * Tests to see if two class objects,
   * or their names are equal.
   *
   * @param first the first object
   * @param second the second object
   * @return true if equal, false if not
   */
  protected static final boolean classEquals(Class first, Class second) {
    return first.equals(second) || first.getName().equals(second.getName());
  }


    /*
     * fields
     */


  /**
   * all accesses to the <code> protected HashMap children </code> field
   * shall be synchronized on that object.
   */
  protected transient HashMap children;

  private int serializable = 0; // children serializable

  /**
   * all accesses to the <code> protected ArrayList bcmListeners </code> field
   * shall be synchronized on that object.
   */
  protected transient ArrayList bcmListeners;

  //

  /**
   * The current locale of this BeanContext.
   */
  protected Locale locale;

  /**
   * A <tt>boolean</tt> indicating if this
   * instance may now render a GUI.
   */
  protected boolean okToUseGui;


  /**
   * A <tt>boolean</tt> indicating whether or not
   * this object is currently in design time mode.
   */
  protected boolean designTime;

    /*
     * transient
     */

  private transient PropertyChangeListener childPCL;

  private transient VetoableChangeListener childVCL;

  private transient boolean serializing;
}
